ホームに戻る
出典 :
関連 :
目次 :
タプル(tuple)とは
複数の値を組にしたデータ構造のこと。
過去にはTuple型、匿名型を用いることができたが、C#7.0より ValueTuple 型が追加された。
以下、特記なき限りは ValueTuple 型について論じるものとする。
構造体(struct)との違い
複数の値を組にできる点は構造体と同様であるが、タプル( ValueTuple )の場合は要素(フィールド)名を必要としないなど、構造体よりも簡便に用いることができる。
(逆に、Tuple 型では要素名を割り当てることができない。)
構造体と異なり事前に型定義を必要としないため、特にメソッドの戻り値として用いる場合に有用である。
構造体
メソッドの戻り値として用いる場合、事前に型定義が必要となる。
// 構造体型 Hoge 定義
struct Hoge
{
public int no;
public string name;
}
:
// Hoge 型を返すメソッド Func()
public Hoge Func()
{
Hoge retval;
retval.no = 20;
retval.name "ttt";
return retval;
}
タプル(ValueTuple)
メソッドの戻り値に用いる場合も、事前の型定義が不要で、簡便に記述できる。
// タプルを返すメソッド Func()
// 要素名( no 、name )は省略可能
public (int no, string name) Func()
{
return (20, "ttt");
}
使用例
タプルの宣言と代入
宣言、代入のいずれのタイミングでもフィールド名を指定(変更)できる。また、すべてのフィールドに名前をつける必要はない(有名・無名フィールドが混在してもよい)。
// 名前なしタプル
(int, string, bool, double) unnamed_tuple = (100, "abcde", true, 1.25);
// 宣言時に名前を指定
(int value1, string value2, bool value3, double value4) named_tuple1 = (100, "abcde", true, 1.25);
// 代入時に名前を指定
(int, string, bool, double) named_tuple2 = (value1: 100, value2: "abcde", value3: true, value4: 1.25);
// 型推論を使用可能
var named_tuple3 = (value1: 100, value2: "abcde", value3: true, value4: 1.25);
タプルの参照
設定された要素(フィールド)名、または先頭要素から順に Item1 、Item2 、… でアクセスが可能。0 起算ではない点に注意。
フィールド名を設定している場合でも Item1 、Item2 、… を用いることができる。
// 名前なしタプルの参照(Item#)
System.Console.WriteLine(unnamed_tuple.Item1); //< 要素1 ⇒ 100
System.Console.WriteLine(unnamed_tuple.Item2); //< 要素2 ⇒ "abcde"
System.Console.WriteLine(unnamed_tuple.Item3); //< 要素3 ⇒ true
System.Console.WriteLine(unnamed_tuple.Item4); //< 要素4 ⇒ 1.25
// 名前つきタプルの参照
System.Console.WriteLine(named_tuple1.value1); //< 要素1 ⇒ 100
System.Console.WriteLine(named_tuple1.value2); //< 要素2 ⇒ "abcde"
System.Console.WriteLine(named_tuple1.value3); //< 要素3 ⇒ true
System.Console.WriteLine(named_tuple1.value4); //< 要素4 ⇒ 1.25
タプルの分解(要素の抽出)
ここで、unnamed_tuple は ValueTuple 型の変数とする。代入により、タプルの各要素を個別の変数に転写できる。
// 1 : 個別に型推論
(var value1, var value2, var value3, var value4) = unnamed_tuple;
// 2 : まとめて型推論
// var ()内に変数を宣言
var (value1, value2, value3, value4) = unnamed_tuple;
// 3 : 要素1、要素3のみを取得(他を読み飛ばす)
var (valueM, _, valueN, _) = unnamed_tuple;
// 4 : 既存の変数をタプル化
int field1;
string field2;
bool field3;
double field4;
(field1, field2, field3, field4) = unnamed_tuple;
上記の例1および2では、変数 value1 に unnamed_tuple.Item1 の値がコピーされる。value1 の型は初期値である unnamed_tuple.Item1 より推論される。
value2 から value4 も同様。
例3のように、抽出する必要のない要素は _ (アンダースコア)を指定することで読み飛ばすことができる。
タプルの比較
すべてのフィールドの値が等しい場合、ふたつのタプルは等しいとみなされる。
フィールド名は比較に関与しない(フィールド名が異なっていても問題とはならない)。
但し、両者の要素数が異なる場合は比較できず、例外が発生する。
var t1 = ( 100, "abcde", true, 1.25); //< 名前なし
var t2 = (value1: 100, value2: "abcde", value3: true, value4: 1.25); //< 名前つき
System.Console.WriteLine(t1 == t2); //< true
var t3 = (100, "abcde", true, 1.25);
var t4 = (100, "abcde", true);
System.Console.WriteLine(t3 == t4); //< 要素数が異なるため例外発生
タプルを返すメソッド
class Program
{
// 名前なしタプルを返すメソッド
public static (int, int) TestMethod_unnamed()
{
return (10, 20);
}
// 名前つきタプルを返すメソッド
public static (int value1, int value2) TestMethod_named()
{
return (100, 200);
}
public static void Main()
{
// 1 : 名前なしタプルの受け取り
var a = TestMethod_unnamed();
System.Console.WriteLine( a.Item1 );
System.Console.WriteLine( a.Item2 );
// 2 : 名前なしタプルを名前つきタプルで受け取り
(var val1, var val2) b = TestMethod_unnamed();
System.Console.WriteLine( b.val1 );
System.Console.WriteLine( b.val2 );
// 3 : 名前つきタプルの受け取り
var c = TestMethod_named();
System.Console.WriteLine( c.value1 );
System.Console.WriteLine( c.value2 );
// 4 : 名前つきタプルを別名で受け取り
(var altVal1, var altVal2) d = TestMethod_named();
System.Console.WriteLine( d.altVal1 );
System.Console.WriteLine( d.altVal2 );
}
}
上記のコードはいずれも有効である。
例4のように、メソッドで返されるタプルのフィールド名を新たに割り当てなおすことも可能だが、フィールド名は最後に割り当てたもののみが有効となる。
ValueTuple 、Tuple 、匿名型の比較
|
ValueTuple |
Tuple |
匿名型 |
メンバの名前付け |
可 |
不可 |
可 |
引数・戻り値としての使用 |
可 |
可 |
不可 |
型の種類 |
構造体 |
クラス |
クラス |
メンバの種類 |
フィールド |
プロパティ |
プロパティ |
可変・不変 |
ミュータブル |
イミュータブル |
イミュータブル |
null格納 |
不可 |
可 |
不可 |
// ValueTupleの宣言・初期化
// 明示的に名前を付与しない限り、メンバに名前はつかない
(int, string ) vt1 = ( 1, "vt"); //< 名前なし
var vt2 = ( 1, "vt"); //< 名前なし
ValueTuple<int, string > vt3 = ( 1, "vt"); //< 名前なし
(int x, string y) vt4 = ( 1, "vt"); //< 名前つき(宣言時に名前付与)
var vt5 = (x: 1, y: "vt"); //< 名前つき(初期化時に名前付与)
// 匿名型の宣言・初期化
// クラス型のため new を用いる
var at1 = new { x = 1, y = "a" }; //< 初期化時に名前付与
var m = 1;
var n = "yy";
var at2 = new { m, n }; //< 名前を明示しない場合、
//< 初期化に使用した変数名がメンバ名となる
//< (at2.m == 1 , at2.n == "yy")
// ValueTuple は値型のため、nullを格納できない
// nullを格納したい場合はnull許容型とする必要がある
(int, string) vt6 = null; //< エラー
// Tuple はクラスのため、nullで初期化できる
Tuple<int, string> t1 = null; //< 正常
// ValueTuple は再割り当て可能(mutable)
var vt7 = (1, "vt");
vt6.Item1 = 2; //< 正常
// Tuple 、匿名型は再割り当て不可(immutable)
var t2 = Tuple.Create(1, "vt");
var at3 = new { Item1 = 1, Item2 = "vt" };
t2.Item1 = 2; //< エラー
at3.Item1 = 2; //< エラー
Tuple 型の構築は ValueTuple 型と異なり、テンプレート型引数を指定するか、Tuple.Create() メソッドを用いる必要がある。
ValueTuple 型は括弧内に型を列記するだけでよく簡便である。
ValueTuple は構造体として実装されているため null を格納できないが、値の更新(再割り当て)が可能である。